package top.jfunc.common.utils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 该类来源于#hutool#
 * 
 * 地址：http://git.oschina.net/loolly/hutool
 * 
 * 类工具类 1、扫描指定包下的所有类<br>
 * 参考 http://www.oschina.net/code/snippet_234657_22722
 * 
 * @author seaside_hi, xiaoleilu
 *
 */
public final class ClassUtil{
    private ClassUtil(){}

    /** Class文件扩展名 */
    private static final String CLASS_EXT     = ".class";
    /** Jar文件扩展名 */
    private static final String JAR_FILE_EXT  = ".jar";
    /** 在Jar中的路径jar的扩展名形式 */
    private static final String JAR_PATH_EXT  = ".jar!";
    /** 当Path为文件形式时, path会加入一个表示文件的前缀 */
    private static final String PATH_FILE_PRE = "file:";

    /**
     * 扫描指定包路径下所有包含指定注解的类
     * 
     * @param packageName
     *            包路径
     * @param inJar
     *            在jar包中查找
     * @param annotationClass
     *            注解类
     * @return 类集合
     */
    public static Set<Class<?>> scanPackageByAnnotation(String packageName, boolean inJar, final Class<? extends Annotation> annotationClass){
        return scanPackage(packageName, inJar, clazz -> clazz.isAnnotationPresent(annotationClass));
    }

    /**
     * 扫描指定包路径下所有指定类的子类
     * 
     * @param packageName
     *            包路径
     * @param inJar
     *            在jar包中查找
     * @param superClass
     *            父类
     * @return 类集合
     */
    public static Set<Class<?>> scanPackageBySuper(String packageName, boolean inJar, final Class<?> superClass){
        return scanPackage(packageName, inJar, (clazz)-> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz));
    }

    /**
     * 扫面包路径下满足class过滤器条件的所有class文件 如果包路径为 com.abs + A.class 但是输入
     * abs会产生classNotFoundException 因为className 应该为 com.abs.A
     * 现在却成为abs.A,此工具类对该异常进行忽略处理,有可能是一个不完善的地方，以后需要进行修改
     * 
     * @param packageName
     *            包路径 com | com. | com.abs | com.abs.
     * @param inJar
     *            在jar包中查找
     * @param classFilter
     *            class过滤器，过滤掉不需要的class
     * @return 类集合
     */
    public static Set<Class<?>> scanPackage(String packageName, boolean inJar, ClassFilter classFilter){
        if(StrUtil.isBlank(packageName)){
            packageName = "";
        }
        packageName = getWellFormedPackageName(packageName);

        final Set<Class<?>> classes = new HashSet<Class<?>>();
        for(String classPath : getClassPaths(packageName)){
            // 填充 classes, 并对classpath解码
            classPath = decodeUrl(classPath);
            fillClasses(classPath, packageName, classFilter, classes);
        }
        // 如果在项目的ClassPath中未找到，去系统定义的ClassPath里找
        if(inJar){
            for(String classPath : getJavaClassPaths()){
                // 填充 classes, 并对classpath解码
                classPath = decodeUrl(classPath);
                fillClasses(classPath, new File(classPath), packageName, classFilter, classes);
            }
        }
        return classes;
    }

    /**
     * 获得ClassPath
     * 
     * @param packageName
     *            包名称
     * @return ClassPath路径字符串集合
     */
    public static Set<String> getClassPaths(String packageName){
        String packagePath = packageName.replace(".", "/");
        Enumeration<URL> resources;
        try{
            resources = getClassLoader().getResources(packagePath);
        }
        catch(IOException e){
            throw new RuntimeException(String.format("Loading classPath [%s] error!", packageName), e);
        }
        Set<String> paths = new HashSet<String>();
        while(resources.hasMoreElements()){
            paths.add(resources.nextElement().getPath());
        }
        return paths;
    }

    /**
     * @return 获得Java ClassPath路径，不包括 jre
     */
    public static String[] getJavaClassPaths(){
        return System.getProperty("java.class.path").split(System.getProperty("path.separator"));
    }

    /**
     * 获取当前线程的ClassLoader
     * 
     * @return 当前线程的class loader
     */
    public static ClassLoader getContextClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 获得class loader 若当前线程class loader不存在，取当前类的class loader
     * 
     * @return 类加载器
     */
    public static ClassLoader getClassLoader(){
        ClassLoader classLoader = getContextClassLoader();
        if(classLoader == null){
            classLoader = ClassUtil.class.getClassLoader();
        }
        return classLoader;
    }

    /**
     * 根据指定的类名称加载类
     * 
     * @param className
     *            完整类名
     * @return {Class}
     * @throws ClassNotFoundException
     *             找不到异常
     */
    public static Class<?> loadClass(String className) throws ClassNotFoundException{
        try{
            return ClassUtil.getContextClassLoader().loadClass(className);
        }
        catch(ClassNotFoundException e){
            try{
                return Class.forName(className, false, ClassUtil.getClassLoader());
            }
            catch(ClassNotFoundException ex){
                try{
                    return ClassLoader.class.getClassLoader().loadClass(className);
                }
                catch(ClassNotFoundException exc){
                    throw exc;
                }
            }
        }
    }

    // ---------------------------------------------------------------------------------------------------
    /**
     * 文件过滤器，过滤掉不需要的文件 只保留Class文件、目录和Jar
     */
    private static FileFilter fileFilter = pathname -> isClass(pathname.getName()) || pathname.isDirectory() || isJarFile(pathname);

    /**
     * 改变 com -> com. 避免在比较的时候把比如 completeTestSuite.class类扫描进去，如果没有"."
     * 那class里面com开头的class类也会被扫描进去,其实名称后面或前面需要一个 ".",来添加包的特征
     * 
     * @param packageName
     *            包名
     * @return 格式化后的包名
     */
    private static String getWellFormedPackageName(String packageName){
        return packageName.lastIndexOf('.') != packageName.length() - 1 ? packageName + '.' : packageName;
    }

    /**
     * 去掉指定前缀
     * 
     * @param str
     *            字符串
     * @param prefix
     *            前缀
     * @return 切掉后的字符串，若前缀不是 preffix， 返回原字符串
     */
    private static String removePrefix(String str, String prefix){
        if(str != null && str.startsWith(prefix)){
            return str.substring(prefix.length());
        }
        return str;
    }

    /**
     * 填充满足条件的class 填充到 classes 同时会判断给定的路径是否为Jar包内的路径，如果是，则扫描此Jar包
     * 
     * @param path
     *            Class文件路径或者所在目录Jar包路径
     * @param packageName
     *            需要扫面的包名
     * @param classFilter
     *            class过滤器
     * @param classes
     *            List 集合
     */
    private static void fillClasses(String path, String packageName, ClassFilter classFilter, Set<Class<?>> classes){
        // 判定给定的路径是否为Jar
        int index = path.lastIndexOf(JAR_PATH_EXT);
        if(index != -1){
            // Jar文件
            path = path.substring(0, index + JAR_FILE_EXT.length()); // 截取jar路径

            path = removePrefix(path, PATH_FILE_PRE); // 去掉文件前缀

            processJarFile(new File(path), packageName, classFilter, classes);
        } else{
            fillClasses(path, new File(path), packageName, classFilter, classes);
        }
    }

    /**
     * 填充满足条件的class 填充到 classes
     * 
     * @param classPath
     *            类文件所在目录，当包名为空时使用此参数，用于截掉类名前面的文件路径
     * @param file
     *            Class文件或者所在目录Jar包文件
     * @param packageName
     *            需要扫面的包名
     * @param classFilter
     *            class过滤器
     * @param classes
     *            List 集合
     */
    private static void fillClasses(String classPath, File file, String packageName, ClassFilter classFilter,
            Set<Class<?>> classes){
        if(file.isDirectory()){
            processDirectory(classPath, file, packageName, classFilter, classes);
        } else if(isClassFile(file)){
            processClassFile(classPath, file, packageName, classFilter, classes);
        } else if(isJarFile(file)){
            processJarFile(file, packageName, classFilter, classes);
        }
    }

    /**
     * 处理如果为目录的情况,需要递归调用 fillClasses方法
     * 
     * @param directory
     *            目录
     * @param packageName
     *            包名
     * @param classFilter
     *            类过滤器
     * @param classes
     *            类集合
     */
    private static void processDirectory(String classPath, File directory, String packageName, ClassFilter classFilter,
            Set<Class<?>> classes){

        File[] files = directory.listFiles(fileFilter);
        if(null == files || files.length == 0){
            return;
        }
        for(File file : files){
            fillClasses(classPath, file, packageName, classFilter, classes);
        }
    }

    /**
     * 处理为class文件的情况,填充满足条件的class 到 classes
     * 
     * @param classPath
     *            类文件所在目录，当包名为空时使用此参数，用于截掉类名前面的文件路径
     * @param file
     *            class文件
     * @param packageName
     *            包名
     * @param classFilter
     *            类过滤器
     * @param classes
     *            类集合
     */
    private static void processClassFile(String classPath, File file, String packageName, ClassFilter classFilter,
            Set<Class<?>> classes){
        if(!classPath.endsWith(File.separator)){
            classPath += File.separator;
        }
        String path = file.getAbsolutePath();
        if(StrUtil.isBlank(packageName)){
            path = removePrefix(path, classPath);
        }
        final String filePathWithDot = path.replace(File.separator, ".");

        int subIndex = -1;
        if((subIndex = filePathWithDot.indexOf(packageName)) != -1){
            final int endIndex = filePathWithDot.lastIndexOf(CLASS_EXT);

            final String className = filePathWithDot.substring(subIndex, endIndex);
            fillClass(className, packageName, classes, classFilter);
        }
    }

    /**
     * 处理为jar文件的情况，填充满足条件的class 到 classes
     * 
     * @param file
     *            jar文件
     * @param packageName
     *            包名
     * @param classFilter
     *            类过滤器
     * @param classes
     *            类集合
     */
    private static void processJarFile(File file, String packageName, ClassFilter classFilter, Set<Class<?>> classes){
        try{
            for(JarEntry entry : Collections.list(new JarFile(file).entries())){
                if(isClass(entry.getName())){
                    final String className = entry.getName().replace("/", ".").replace(CLASS_EXT, "");
                    fillClass(className, packageName, classes, classFilter);
                }
            }
        }
        catch(Throwable ex){
        }
    }

    /**
     * 填充class 到 classes
     * 
     * @param className
     *            类名
     * @param packageName
     *            包名
     * @param classes
     *            类集合
     * @param classFilter
     *            类过滤器
     */
    private static void fillClass(String className, String packageName, Set<Class<?>> classes, ClassFilter classFilter){
        if(className.startsWith(packageName)){
            try{
                final Class<?> clazz = ClassUtil.loadClass(className);
                if(classFilter == null || classFilter.accept(clazz)){
                    classes.add(clazz);
                }
            }
            catch(Throwable ex){
            }
        }
    }

    /**
     * @param file
     *            文件
     * @return 是否为类文件
     */
    private static boolean isClassFile(File file){
        return isClass(file.getName());
    }

    /**
     * @param fileName
     *            文件名
     * @return 是否为类文件
     */
    private static boolean isClass(String fileName){
        return fileName.endsWith(CLASS_EXT);
    }

    /**
     * @param file
     *            文件
     * @return 是否为Jar文件
     */
    private static boolean isJarFile(File file){
        return file.getName().endsWith(JAR_FILE_EXT);
    }
    // ---------------------------------------------------------------------------------------------------

    /**
     * 类过滤器，用于过滤不需要加载的类
     */
    public interface ClassFilter{
        boolean accept(Class<?> clazz);
    }

    /**
     * 对路径解码
     * 
     * @param url
     *            路径
     * @return String 解码后的路径
     */
    private static String decodeUrl(String url){
        try{
            return URLDecoder.decode(url, CharsetUtil.UTF_8);
        }
        catch(java.io.UnsupportedEncodingException e){
            throw new RuntimeException(e);
        }
    }

    public static void makeAccessible(Constructor<?> ctor) {
        if ((!Modifier.isPublic(ctor.getModifiers()) ||
                !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) {
            ctor.setAccessible(true);
        }
    }

    /**
     * 确定class是否可以被加载
     * @param classNames 完整类名集合，全部满足才返回true
     * @param classLoader 类加载
     * @return {boolean}
     */
    public static boolean isPresent(ClassLoader classLoader , String... classNames) {
        try {
            for (String className : classNames) {
                Class.forName(className, true, classLoader);
            }
            return true;
        }
        catch (Throwable ex) {
            // Class or one of its dependencies is not present...
            return false;
        }
    }


    public static Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }
}